#include "gtktreemodelfilter.h"
#include "gtkwidgetprivate.h"
#include "gtkstylecontext.h"
+#include "gtksearchbar.h"
+#include "gtksearchentry.h"
+#include "treewalk.h"
enum
{
GHashTable *iters;
gulong map_hook;
gulong unmap_hook;
+ GtkTreeViewColumn *object_column;
+ GtkWidget *search_bar;
+ GtkWidget *search_entry;
+ GtkTreeWalk *walk;
+ gint search_length;
};
static guint signals[LAST_SIGNAL] = { 0 };
GtkInspectorObjectTree *wt)
{
GObject *object;
+ GtkTreeIter iter;
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ gtk_tree_walk_reset (wt->priv->walk, &iter);
+ else
+ gtk_tree_walk_reset (wt->priv->walk, NULL);
object = gtk_inspector_object_tree_get_selected (wt);
g_signal_emit (wt, signals[OBJECT_SELECTED], 0, object);
}
return TRUE;
}
+static void
+move_search_to_row (GtkInspectorObjectTree *wt,
+ GtkTreeIter *iter)
+{
+ GtkTreeSelection *selection;
+ GtkTreePath *path;
+
+ selection = gtk_tree_view_get_selection (wt->priv->tree);
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (wt->priv->model), iter);
+ gtk_tree_view_expand_to_path (wt->priv->tree, path);
+ gtk_tree_selection_select_path (selection, path);
+ gtk_tree_view_scroll_to_cell (wt->priv->tree, path, wt->priv->object_column, FALSE, 0.0, 0.0);
+ gtk_tree_path_free (path);
+}
+
+static gboolean
+key_press_event (GtkWidget *window,
+ GdkEvent *event,
+ GtkInspectorObjectTree *wt)
+{
+ if (gtk_widget_get_mapped (GTK_WIDGET (wt)))
+ {
+ GdkModifierType default_accel;
+ gboolean search_started;
+
+ search_started = gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (wt->priv->search_bar));
+ default_accel = gtk_widget_get_modifier_mask (GTK_WIDGET (wt), GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR);
+
+ if (search_started &&
+ (event->key.keyval == GDK_KEY_Return ||
+ event->key.keyval == GDK_KEY_ISO_Enter ||
+ event->key.keyval == GDK_KEY_KP_Enter))
+ {
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (wt->priv->tree));
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ path = gtk_tree_model_get_path (model, &iter);
+ gtk_tree_view_row_activated (GTK_TREE_VIEW (wt->priv->tree),
+ path,
+ wt->priv->object_column);
+ gtk_tree_path_free (path);
+
+ return GDK_EVENT_STOP;
+ }
+ else
+ return GDK_EVENT_PROPAGATE;
+ }
+ else if (search_started &&
+ (event->key.keyval == GDK_KEY_Escape))
+ {
+ gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (wt->priv->search_bar), FALSE);
+ return GDK_EVENT_STOP;
+ }
+ else if (search_started &&
+ ((event->key.state & (default_accel | GDK_SHIFT_MASK)) == (default_accel | GDK_SHIFT_MASK)) &&
+ (event->key.keyval == GDK_KEY_g || event->key.keyval == GDK_KEY_G))
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_walk_next_match (wt->priv->walk, TRUE, TRUE, &iter))
+ move_search_to_row (wt, &iter);
+ else
+ gtk_widget_error_bell (GTK_WIDGET (wt));
+
+ return GDK_EVENT_STOP;
+ }
+ else if (search_started &&
+ ((event->key.state & (default_accel | GDK_SHIFT_MASK)) == default_accel) &&
+ (event->key.keyval == GDK_KEY_g || event->key.keyval == GDK_KEY_G))
+ {
+ GtkTreeIter iter;
+
+ if (gtk_tree_walk_next_match (wt->priv->walk, TRUE, FALSE, &iter))
+ move_search_to_row (wt, &iter);
+ else
+ gtk_widget_error_bell (GTK_WIDGET (wt));
+
+ return GDK_EVENT_STOP;
+ }
+
+ return gtk_search_bar_handle_event (GTK_SEARCH_BAR (wt->priv->search_bar), event);
+ }
+ else
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+on_hierarchy_changed (GtkWidget *widget,
+ GtkWidget *previous_toplevel)
+{
+ if (previous_toplevel)
+ g_signal_handlers_disconnect_by_func (previous_toplevel, key_press_event, widget);
+ g_signal_connect (gtk_widget_get_toplevel (widget), "key-press-event",
+ G_CALLBACK (key_press_event), widget);
+}
+
+static void
+on_search_changed (GtkSearchEntry *entry,
+ GtkInspectorObjectTree *wt)
+{
+ GtkTreeIter iter;
+ gint length;
+ gboolean backwards;
+
+ length = strlen (gtk_entry_get_text (GTK_ENTRY (entry)));
+ backwards = length < wt->priv->search_length;
+ wt->priv->search_length = length;
+
+ if (length == 0)
+ return;
+
+ if (gtk_tree_walk_next_match (wt->priv->walk, backwards, backwards, &iter))
+ move_search_to_row (wt, &iter);
+ else if (!backwards)
+ gtk_widget_error_bell (GTK_WIDGET (wt));
+}
+
+static gboolean
+match_string (const gchar *string,
+ const gchar *text)
+{
+ gchar *lower;
+ gboolean match = FALSE;
+
+ if (string)
+ {
+ lower = g_ascii_strdown (string, -1);
+ match = g_str_has_prefix (lower, text);
+ g_free (lower);
+ }
+
+ return match;
+}
+
+static gboolean
+match_row (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ GtkInspectorObjectTree *wt = data;
+ gchar *type, *name, *label;
+ const gchar *text;
+ gboolean match;
+
+ text = gtk_entry_get_text (GTK_ENTRY (wt->priv->search_entry));
+ gtk_tree_model_get (model, iter,
+ OBJECT_TYPE, &type,
+ OBJECT_NAME, &name,
+ OBJECT_LABEL, &label,
+ -1);
+
+ match = (match_string (type, text) ||
+ match_string (name, text) ||
+ match_string (label, text));
+
+ g_free (type);
+ g_free (name);
+ g_free (label);
+
+ return match;
+}
+
+static void
+search_mode_changed (GObject *search_bar,
+ GParamSpec *pspec,
+ GtkInspectorObjectTree *wt)
+{
+ if (!gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (search_bar)))
+ {
+ gtk_tree_walk_reset (wt->priv->walk, NULL);
+ wt->priv->search_length = 0;
+ }
+}
+
+static void
+next_match (GtkButton *button,
+ GtkInspectorObjectTree *wt)
+{
+ if (gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (wt->priv->search_bar)))
+ {
+ GtkTreeIter iter;
+
+ if (gtk_tree_walk_next_match (wt->priv->walk, TRUE, FALSE, &iter))
+ move_search_to_row (wt, &iter);
+ else
+ gtk_widget_error_bell (GTK_WIDGET (wt));
+ }
+}
+
+static void
+previous_match (GtkButton *button,
+ GtkInspectorObjectTree *wt)
+{
+ if (gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (wt->priv->search_bar)))
+ {
+ GtkTreeIter iter;
+
+ if (gtk_tree_walk_next_match (wt->priv->walk, TRUE, TRUE, &iter))
+ move_search_to_row (wt, &iter);
+ else
+ gtk_widget_error_bell (GTK_WIDGET (wt));
+ }
+}
+
static void
gtk_inspector_object_tree_init (GtkInspectorObjectTree *wt)
{
(GDestroyNotify) object_data_free);
gtk_widget_init_template (GTK_WIDGET (wt));
+ gtk_search_bar_connect_entry (GTK_SEARCH_BAR (wt->priv->search_bar),
+ GTK_ENTRY (wt->priv->search_entry));
+
+ g_signal_connect (wt->priv->search_bar, "notify::search-mode-enabled",
+ G_CALLBACK (search_mode_changed), wt);
+ wt->priv->walk = gtk_tree_walk_new (GTK_TREE_MODEL (wt->priv->model), match_row, wt, NULL);
+
signal_id = g_signal_lookup ("map", GTK_TYPE_WIDGET);
wt->priv->map_hook = g_signal_add_emission_hook (signal_id, 0,
map_or_unmap, wt, NULL);
signal_id = g_signal_lookup ("unmap", GTK_TYPE_WIDGET);
g_signal_remove_emission_hook (signal_id, wt->priv->unmap_hook);
+ gtk_tree_walk_free (wt->priv->walk);
+
G_OBJECT_CLASS (gtk_inspector_object_tree_parent_class)->finalize (object);
}
gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/inspector/object-tree.ui");
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorObjectTree, model);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorObjectTree, tree);
+ gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorObjectTree, object_column);
+ gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorObjectTree, search_bar);
+ gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorObjectTree, search_entry);
gtk_widget_class_bind_template_callback (widget_class, on_selection_changed);
gtk_widget_class_bind_template_callback (widget_class, on_row_activated);
+ gtk_widget_class_bind_template_callback (widget_class, on_hierarchy_changed);
+ gtk_widget_class_bind_template_callback (widget_class, on_search_changed);
+ gtk_widget_class_bind_template_callback (widget_class, next_match);
+ gtk_widget_class_bind_template_callback (widget_class, previous_match);
}
typedef struct
<template class="GtkInspectorObjectTree" parent="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
+ <signal name="hierarchy-changed" handler="on_hierarchy_changed"/>
+ <child>
+ <object class="GtkSearchBar" id="search_bar">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">horizontal</property>
+ <style>
+ <class name="linked"/>
+ </style>
+ <child>
+ <object class="GtkSearchEntry" id="search_entry">
+ <property name="visible">True</property>
+ <property name="max-width-chars">40</property>
+ <signal name="search-changed" handler="on_search_changed"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="next_match"/>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">go-down-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="previous_match"/>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">go-up-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<object class="GtkTreeView" id="tree">
<property name="visible">True</property>
<property name="model">model</property>
- <property name="enable-search">True</property>
- <property name="search-column">2</property>
+ <property name="enable-search">False</property>
<signal name="row-activated" handler="on_row_activated"/>
<child internal-child="selection">
<object class="GtkTreeSelection">
</object>
</child>
<child>
- <object class="GtkTreeViewColumn">
+ <object class="GtkTreeViewColumn" id="object_column">
<property name="title" translatable="yes">Object</property>
<property name="resizable">True</property>
<child>
--- /dev/null
+/*
+ * Copyright (c) 2014 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "treewalk.h"
+
+struct _GtkTreeWalk
+{
+ GtkTreeModel *model;
+ GtkTreeIter position;
+ gboolean visited;
+ RowPredicate predicate;
+ gpointer data;
+ GDestroyNotify destroy;
+};
+
+GtkTreeWalk *
+gtk_tree_walk_new (GtkTreeModel *model,
+ RowPredicate predicate,
+ gpointer data,
+ GDestroyNotify destroy)
+{
+ GtkTreeWalk *walk;
+
+ walk = g_new (GtkTreeWalk, 1);
+ walk->model = g_object_ref (model);
+ walk->visited = FALSE;
+ walk->predicate = predicate;
+ walk->data = data;
+ walk->destroy = destroy;
+
+ return walk;
+}
+
+void
+gtk_tree_walk_free (GtkTreeWalk *walk)
+{
+ g_object_unref (walk->model);
+
+ if (walk->destroy)
+ walk->destroy (walk->data);
+
+ g_free (walk);
+}
+
+void
+gtk_tree_walk_reset (GtkTreeWalk *walk,
+ GtkTreeIter *iter)
+{
+ if (iter)
+ {
+ walk->position = *iter;
+ walk->visited = TRUE;
+ }
+ else
+ {
+ walk->visited = FALSE;
+ }
+}
+
+static gboolean
+gtk_tree_walk_step_forward (GtkTreeWalk *walk)
+{
+ GtkTreeIter next, up;
+
+ if (!walk->visited)
+ {
+ if (!gtk_tree_model_get_iter_first (walk->model, &walk->position))
+ return FALSE;
+
+ walk->visited = TRUE;
+ return TRUE;
+ }
+
+ if (gtk_tree_model_iter_children (walk->model, &next, &walk->position))
+ {
+ walk->position = next;
+ return TRUE;
+ }
+
+ next = walk->position;
+ do
+ {
+ up = next;
+ if (gtk_tree_model_iter_next (walk->model, &next))
+ {
+ walk->position = next;
+ return TRUE;
+ }
+ }
+ while (gtk_tree_model_iter_parent (walk->model, &next, &up));
+
+ return FALSE;
+}
+
+static gboolean
+gtk_tree_model_iter_last_child (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent)
+{
+ GtkTreeIter next;
+
+ if (!gtk_tree_model_iter_children (model, &next, parent))
+ return FALSE;
+
+ do
+ *iter = next;
+ while (gtk_tree_model_iter_next (model, &next));
+
+ return TRUE;
+}
+
+static gboolean
+gtk_tree_model_get_iter_last (GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ GtkTreeIter next;
+
+ if (!gtk_tree_model_iter_last_child (model, &next, NULL))
+ return FALSE;
+
+ do
+ *iter = next;
+ while (gtk_tree_model_iter_last_child (model, &next, &next));
+
+ return TRUE;
+}
+
+static gboolean
+gtk_tree_walk_step_back (GtkTreeWalk *walk)
+{
+ GtkTreeIter previous, down;
+
+ if (!walk->visited)
+ {
+ if (!gtk_tree_model_get_iter_last (walk->model, &walk->position))
+ return FALSE;
+
+ walk->visited = TRUE;
+ return TRUE;
+ }
+
+ previous = walk->position;
+ if (gtk_tree_model_iter_previous (walk->model, &previous))
+ {
+ while (gtk_tree_model_iter_last_child (walk->model, &down, &previous))
+ previous = down;
+
+ walk->position = previous;
+ return TRUE;
+ }
+
+ if (gtk_tree_model_iter_parent (walk->model, &previous, &walk->position))
+ {
+ walk->position = previous;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gtk_tree_walk_step (GtkTreeWalk *walk, gboolean backwards)
+{
+ if (backwards)
+ return gtk_tree_walk_step_back (walk);
+ else
+ return gtk_tree_walk_step_forward (walk);
+}
+
+static gboolean
+row_is_match (GtkTreeWalk *walk)
+{
+ if (walk->predicate)
+ return walk->predicate (walk->model, &walk->position, walk->data);
+ return TRUE;
+}
+
+gboolean
+gtk_tree_walk_next_match (GtkTreeWalk *walk,
+ gboolean force_move,
+ gboolean backwards,
+ GtkTreeIter *iter)
+{
+ gboolean moved = FALSE;
+ gboolean was_visited;
+ GtkTreeIter position;
+
+ was_visited = walk->visited;
+ position = walk->position;
+
+ do
+ {
+ if (moved || (!force_move && walk->visited))
+ {
+ if (row_is_match (walk))
+ {
+ *iter = walk->position;
+ return TRUE;
+ }
+ }
+ moved = TRUE;
+ }
+ while (gtk_tree_walk_step (walk, backwards));
+
+ walk->visited = was_visited;
+ walk->position = position;
+
+ return FALSE;
+}